﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;
using System.Reflection;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using System.Globalization;
using Wire;
using Timer = System.Threading.Timer;
using Resources = QuickTools.MatchmediaHelper.Properties.Resources;
using Settings = QuickTools.MatchmediaHelper.Properties.Settings;
using System.Management;
using QuickTools.PluginUpdater;


namespace QuickTools.MatchmediaHelper
{
    public class ReplayHelper : Wire.Plugin
    {
        #region private variables
        private const string PluginTitle = "quickTools MatchmediaHelper";
        private const string ShortTitle = "MatchmediaHelper";
        private GameInterface gameInterface;

        private MatchData matchData = null;

        private bool replayWarningActive = true;
        private bool isReplayRecording = false;
        private ReplayWatcher replayWatcher;
        private Timer osdTimer;
        private ContextMenuStrip contextMenu = new ContextMenuStrip();
        private FileSystemWatcher screenshotWatcher = new FileSystemWatcher();
        private WirePluginUpdater updater = new WirePluginUpdater(Properties.Settings.Default.UpdateUrl, version);
        #endregion

        #region Constructors

        static ReplayHelper()
        {
            if (Settings.Default.NewVersionInstalled)
            {
                Settings.Default.Upgrade();
                Settings.Default.NewVersionInstalled = false;
                Settings.Default.Save();
            }
            
            Application.EnableVisualStyles();
        }

        public ReplayHelper()
        {
            osdTimer = new Timer(new TimerCallback(OsdTimerElapsed));
            initContextMenu(contextMenu);

            screenshotWatcher.Created += new FileSystemEventHandler(screenshotWatcher_Created);
            screenshotWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime;
            updater.UpdateCheckComplete += new EventHandler<UpdateCheckEventArgs>(updater_UpdateCheckComplete);
            updater.Channel = Properties.Settings.Default.SelectedUpdateChannel;
            Settings.Default.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Default_PropertyChanged);

            if (Settings.Default.CheckForUpdatesOnStartup)
                updater.CheckForUpdateAsync(true, false);
        }

        void Default_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "SelectedUpdateChannel":
                    updater.Channel = Settings.Default.SelectedUpdateChannel;
                    break;
                case "UpdateUrl":
                    updater.UpdateUrl = Settings.Default.UpdateUrl;
                    break;
            }
        }

        #endregion

        #region private classes
        private class MatchData
        {
            public MatchData(int id, string matchMediaDirectory)
            {
                this.id = id;
                this.matchMediaDirectory = matchMediaDirectory;
            }

            int id;
            public int Id
            {
                get { return id; }
            }

            string matchMediaDirectory;
            public string MatchMediaDirectory
            {
                get { return matchMediaDirectory; }
            }
        }

        private class ScreenshotConvertData
        {
            public ScreenshotConvertData(string sourcePath, MatchData matchData)
            {
                SourcePath = sourcePath;
                MatchData = matchData;
            }
            public string SourcePath { get; private set; }
            public MatchData MatchData { get; private set; }
        }
        #endregion

        #region plugin interface

        public override void iconClicked(int x, int y, Plugin.MouseButton button)
        {
            switch (button)
            {
                case MouseButton.RightButton:
                    contextMenu.Show(x, y);
                    break;
                case MouseButton.LeftButton:
                    ShowSettings(null, EventArgs.Empty);
                    break;
            }
            base.iconClicked(x, y, button);
        }

        public override string Author
        {
            get { return "Lucas Romero"; }
        }

        public override string Title
        {
            get { return PluginTitle; }
        }

        private static readonly Version version = typeof(ReplayHelper).Assembly.GetName().Version;
        public override string Version
        {
            get
            {
                return version.ToString();
            }
        }

        // Init the plugin
        public override void init()
        {
            try
            {
                Debug.WriteLine("Executing assembly: " + Assembly.GetExecutingAssembly().Location);
                string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                FileInfo oldplugin = new FileInfo(Path.Combine(Path.Combine(dir, ".."), "quickTools.MatchmediaHelper.dll"));
                if (oldplugin.Exists)
                {
                    Debug.WriteLine("Looking for " + oldplugin.FullName);
                    Trace.WriteLine("Using WMI to get param info");
                    //Use WMI to get the commandline-parameters hl/hl2 was started with
                    ManagementObjectSearcher query = new ManagementObjectSearcher(String.Format("SELECT * FROM Win32_Process WHERE Name='{0}'", "wire-plugin.exe"));
                    ManagementObjectCollection queryCollection = query.Get();
                    Debug.WriteLine(queryCollection.Count.ToString() + " instances found!");

                    foreach (ManagementObject mo in queryCollection)
                    {
                        string cmdline = mo["CommandLine"].ToString().ToLowerInvariant();
                        int pid = Int32.Parse(mo["ProcessID"].ToString());
                        Debug.WriteLine(String.Format("PID [ {0} ] started '{1}' With commandline - '{2}'", pid, mo["CreationDate"].ToString(), cmdline));
                        string tmpName = oldplugin.FullName.Replace('\\', '/').ToLowerInvariant();
                        Debug.WriteLine("Comparing with " + tmpName);
                        if (cmdline.Contains(tmpName))
                        {
                            Debug.WriteLine("Stopping process");
                            Process p = Process.GetProcessById(pid);
                            p.Kill();
                            p.WaitForExit();
                            Debug.WriteLine("Deleting files");
                            oldplugin.Delete();
                            FileInfo config = new FileInfo(oldplugin.FullName + ".config");
                            if (config.Exists)
                                config.Delete();
                            DirectoryInfo resDir = new DirectoryInfo(Path.Combine(oldplugin.DirectoryName, "de"));
                            if (resDir.Exists)
                            {
                                FileInfo[] files = resDir.GetFiles();
                                if (files.Length == 0)
                                    resDir.Delete();
                                else if (files.Length == 1 && files[0].Name == "quickTools.MatchmediaHelper.resources.dll")
                                    resDir.Delete(true);
                            }
                            // try to delete dependency if not in use
                            File.Delete(Path.Combine(oldplugin.DirectoryName, "ICSharpCode.SharpZipLib.dll"));
                        }
                    }
                }
            }
            catch (System.Exception e)
            {
                Trace.WriteLine("Error while looking for old plugin: " + e.ToString());
            }

            gameInterface = InterfaceFactory.gameInterface();
            gameInterface.MatchStarted += new GameInterface.MatchStartedHandler(gameInterface_MatchStarted);
            gameInterface.GameStarted += new GameInterface.GameStartedHandler(m_gameInterface_GameStarted);
            gameInterface.GameStopped += new GameInterface.GameStoppedHandler(m_gameInterface_GameStopped);
            // show icon
            setIcon(Resources.RecordButton);
            try
            {
                replayWatcher = new ReplayWatcher(gameInterface);
                replayWatcher.ReplayStatusChanged += new EventHandler<ReplayStatusChangedEventArgs>(replayWatcher_ReplayStatusChanged);
            }
            catch (Exception ex)
            {
                string errorMsg = String.Format("Error initializing ReplayWatcher: {0}", ex);
                Trace.WriteLine(errorMsg);
            }
        }

        #endregion

        #region Context menu
        private void initContextMenu(ContextMenuStrip menu)
        {
            menu.RenderMode = ToolStripRenderMode.ManagerRenderMode;
            menu.Font = new Font("Tahoma", 8);
            menu.ShowCheckMargin = false;
            menu.ShowItemToolTips = false;
            menu.AutoClose = true;
            

            menu.Items.Add(Resources.IgnoreWarnings, Resources.error, new EventHandler(IgnoreWarnings));
            menu.Items.Add(new ToolStripSeparator());
            menu.Items.Add(Resources.Settings, Resources.wrench, new EventHandler(ShowSettings));
            menu.Items.Add(Resources.CheckForUpdatesMenuItemTitle, Resources.box_search_result, new EventHandler(CheckForUpdate));
            menu.Items.Add(Resources.ReportBug, Resources.bug, new EventHandler(ReportBug));
            menu.Items.Add(Resources.About + " " + ShortTitle, Resources.information, new EventHandler(About));
            menu.Items.Add(new ToolStripSeparator());
            menu.Items.Add(Resources.CloseMenu, Resources.cancel);
        }

        private void ReportBug(object sender, EventArgs e)
        {
            try
            {
                Process.Start(@"http://code.google.com/p/quicktools/issues/list");
            }
            catch (Exception ex)
            {
                MessageBox.Show(String.Format(Resources.StartBrowserError, ex.Message), "Error");
            }
        }

        private void CheckForUpdate(object sender, EventArgs e)
        {
            updater.CheckForUpdateAsync(true, true);
        }

        private void About(object sender, EventArgs e)
        {
            using (FormAbout f = new FormAbout())
            {
                f.ShowDialog();
            }
        }

        private void IgnoreWarnings(object sender, EventArgs e)
        {
            //Stop();
            replayWarningActive = false;

        }

        private void ShowSettings(object sender, EventArgs e)
        {
            using (FormOptions o = new FormOptions())
            {
                o.ShowDialog();
            }
        }

        #endregion

        private void updater_UpdateCheckComplete(object sender, UpdateCheckEventArgs e)
        {
            if (e.HasError)
            {
                if (e.Parameters.HandleErrors)
                    MessageBox.Show(String.Format(Resources.UpdateErrorText, e.Error.Message), Resources.UpdateErrorTitle);
                return;
            }
            lock (Settings.Default.AvailableUpdateChannels)
            {
                Settings.Default.AvailableUpdateChannels.Clear();
                foreach (UpdateChannel c in e.Updates.Channels)
                {
                    Settings.Default.AvailableUpdateChannels.Add(c.Name);
                }
                Settings.Default.Save();
            }
            try
            {
                WirePluginUpdater u = sender as WirePluginUpdater;
                Update update = u.GetPreferredUpdate(e.Updates);
#if DEBUG
                update = update ?? e.Updates.Updates[0];
#endif
                if (update != null)
                {
                    Thread t = new Thread(new ParameterizedThreadStart(ShowUpdateWindow));
                    t.SetApartmentState(ApartmentState.STA);
                    t.Start(new ShowUpdateWindowParams { Updater = u, Updates = e.Updates, Update = update });
                    t.Join();
                }
                else if (e.Parameters.ShowMessageWhenNoUpdateAvailable)
                    MessageBox.Show(Resources.UpdateUpToDateText, Resources.UpdateUpToDateTitle);
            }
            catch (Exception ex)
            {
                MessageBox.Show(String.Format(Resources.UpdateErrorText, ex.Message), Resources.UpdateErrorTitle);
            }
        }
        private class ShowUpdateWindowParams
        {
            public WirePluginUpdater Updater{get;set;}
            public UpdateCollection Updates{get;set;}
            public Update Update {get;set;}
        }

        private void ShowUpdateWindow(object state)
        {
            ShowUpdateWindowParams p = state as ShowUpdateWindowParams;
            FormUpdateAvailable f = new FormUpdateAvailable(p.Updater, p.Updates, p.Update);
            DialogResult choice = f.ShowDialog();
            /*DialogResult choice = MessageBox.Show(
                String.Format(Resources.UpdateAvailableText, update.Version, version, update.Channel),
                Resources.UpdateAvailableTitle,
                MessageBoxButtons.YesNo);*/
            if (choice == DialogResult.Yes)
            {
                p.Update.DownloadAsync();
                MessageBox.Show(Resources.UpdateInstallingText, Resources.UpdateInstallingTitle);
            }
        }

        private void OsdTimerElapsed(object dummy)
        {
            if (!replayWarningActive)
                return;
            if (!isReplayRecording)
            {
                try
                {
                    // show warning
                    gameInterface.showInGameNotification(
                        Resources.RecordButton,
                        Resources.OsdNotRecordingTitle,
                        Resources.OsdNotRecordingText,
                        Settings.Default.ReminderDuration);
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.ToString());
                }
                osdTimer.Change(Settings.Default.ReminderInterval, Timeout.Infinite);
            }
        }

        private void gameInterface_MatchStarted(int matchId, string matchMediaPath)
        {
            matchData = new MatchData(matchId, matchMediaPath);
        }

        private void m_gameInterface_GameStopped(int gameId)
        {
            Trace.WriteLine(String.Format("Game {0} ended", gameId));
            Stop();
        }

        private void replayWatcher_ReplayStatusChanged(object sender, ReplayStatusChangedEventArgs e)
        {
            if (e.Recording != isReplayRecording)
            {
                try
                {
                    isReplayRecording = e.Recording;
                    int dueTime = isReplayRecording ? Timeout.Infinite : 0;
                    osdTimer.Change(dueTime, Timeout.Infinite);

                    if (isReplayRecording)
                    {
                        try
                        {
                            gameInterface.showInGameNotification(
                                Resources.RecordRedButton,
                                Resources.OsdRecordingStartedTitle,
                                String.Format(Resources.OsdRecordingStartedText, Path.GetFileName(e.FullName)),
                                5000);
                        }
                        catch (Exception ex)
                        {
                            Trace.WriteLine(ex.ToString());
                        }
                    }
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.ToString());
                }
            }
        }

        public void Stop()
        {
            screenshotWatcher.EnableRaisingEvents = false;
            osdTimer.Change(Timeout.Infinite, Timeout.Infinite);
            matchData = null;
            isReplayRecording = false;
            replayWatcher.Stop();
            try
            {
                setIcon(Resources.RecordButton);
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.ToString());
            }
        }

        private void m_gameInterface_GameStarted(int gameId, string gamePath)
        {
            // Counter-Strike's game id is 61
            // Counter-Strike Source game id is 60
            Trace.WriteLine(String.Format("Game {0} started from path {1}", gameId, gamePath));
            var gameInfo = gameInterface.gameInfo(gameId);
            Trace.WriteLine(String.Format("Commandline: {0}", gameInfo["commandLine"]));
            string screenDir = null;
            string filter = "*.bmp";
            string replayFilter = "*.dem";
            List<string> watchDirs = new List<string>();
            try
            {
                string parentDir = Path.GetDirectoryName(gamePath);
                string modDir = parentDir;
                switch (gameId)
                {
                    case 43: // Halfe-Life 2
                    case 60: // Counter-Strike Source
                    case 112: // Day of Defeat: Source
                    case 126: // Team Fortress 2
                    case 182: // Half-Life 2: Deathmatch
                    case 5484: // CS Promod
                        modDir = Utils.GetModFromDir(parentDir, Path.Combine("resource","game.ico"));
                        watchDirs.Add(Path.Combine(parentDir, modDir));
                        screenDir = Path.Combine(modDir, "screenshots");
                        filter = "*.jpg";
                        break;
                    case 59: // Counter-Strike: Condition Zero
                    case 61: // Counter-Strike 1.6
                    case 64: // Day of Defeat
                        modDir = Utils.GetModFromDir(parentDir, "game.ico");
                        watchDirs.Add(Path.Combine(parentDir, modDir));
                        screenDir = modDir;
                        filter = "*.bmp";
                        break;
                    case 3580:  // Call of Duty 4: Modern Warfare
                        //modDir = Utils.GetModFromDir(Path.Combine(parentDir, "Mods"), "console_mp.log");
                        DirectoryInfo d = new DirectoryInfo(Path.Combine(parentDir, "mods"));
                        if (!d.Exists)
                            return;
                        DirectoryInfo[] dirs = d.GetDirectories();
                        foreach(DirectoryInfo curModDir in dirs)
                        {
                            watchDirs.Add(Path.Combine(curModDir.FullName, "demos"));
                        }
                        //screenDir = Path.Combine(modDir, "screenshots");
                        filter = "*.jpg";
                        //modDir = Path.Combine(modDir, "demos");
                        replayFilter = "*.dm_1";
                        break;
                    case 5688:  // Call of Duty: Black Ops
                        // static path
                        watchDirs.Add(Path.Combine(parentDir, "demos"));
                        screenDir = Path.Combine(parentDir, "screenshots");
                        filter = "*.jpg";
                        replayFilter = "*.dm_7";
                        break;
                    default:
                        Debug.WriteLine("Game not supported, ignoring");
                        return;
                }

                replayWatcher.Start(replayFilter, watchDirs.ToArray());

                // only activate warnings if this is an "official" match
                replayWarningActive = !Settings.Default.OnlyWarnInOfficialMatches || matchData != null;

                if (replayWarningActive)
                    setIcon(Resources.RecordRedButton);
                osdTimer.Change(Settings.Default.ReminderInterval, Timeout.Infinite);

                // only enable screenshot monitor if this is an "official" match
                if (matchData != null)
                {
                    if (Settings.Default.ScreenshotWatcherEnabled && !string.IsNullOrEmpty(screenDir))
                    {
                        string screenPath = Path.Combine(parentDir, screenDir);
                        if (!Directory.Exists(screenPath))
                            Directory.CreateDirectory(screenPath);
                        screenshotWatcher.Filter = filter;
                        screenshotWatcher.Path = screenPath;
                        screenshotWatcher.EnableRaisingEvents = true;
                        LogTrace("Monitoring '{0}' for screenshots", screenshotWatcher.Path);
                    }
                    else
                        LogTrace("Screenshotwatcher disabled or screenDir empty");
                }
                else
                    LogTrace("Not an official match, not starting screenshot watcher");
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex);
            }
        }

        [Conditional("DEBUG")]
        private void LogDebug(string format, params object[] args)
        {
            Debug.WriteLine(String.Format(format, args));
        }

        [Conditional("TRACE")]
        private void LogTrace(string format, params object[] args)
        {
            Trace.WriteLine(String.Format(format, args));
        }

        void screenshotWatcher_Created(object sender, FileSystemEventArgs e)
        {
            MatchData m = matchData;
            if (m != null)
            {
                try
                {
                    LogDebug("Got event for screenshot {0}: {1}", e.FullPath, e.ChangeType);
                    string sourceFile = e.FullPath;
                    if (string.Compare(Path.GetExtension(sourceFile), ".bmp", true) == 0)
                    {
                        // start thread to convert bmp to jpeg and return
                        ThreadPool.QueueUserWorkItem(new WaitCallback(ConvertBmpFileAsync), new ScreenshotConvertData(sourceFile, m));
                        return;
                    }
                    // file is not a bmp file, copy directly
                    // (we only watch for *jpg and *.bmp, so it has to be a jpg at this point)
                    string newFileName = Path.ChangeExtension(Path.GetFileName(sourceFile), ".jpg");
                    MoveScreenshotAndShowMessage(sourceFile, newFileName);
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.ToString());
                }
            }
        }

        private void MoveScreenshotAndShowMessage(string sourceFile, string targetName)
        {
            gameInterface.moveToMatchMedia(sourceFile, targetName, matchData.Id);
            gameInterface.showInGameNotification(Resources.ScreenshotCapturedTitle, String.Format(Resources.ScreenshotCapturedText, Path.GetFileName(targetName)), 5000);
        }

        private void ConvertBmpFileAsync(object data)
        {
            try
            {
                ScreenshotConvertData d = data as ScreenshotConvertData;
                FileInfo i = new FileInfo(d.SourcePath);
                bool done = false;
                string tmpFile = null;
                Stopwatch w = new Stopwatch();
                w.Start();
                // wait until the file was written completely
                do
                {
                    try
                    {
                        tmpFile = Utils.GetTemporaryJpeg(d.SourcePath, Settings.Default.JpegQuality);
                        done = true;
                    }
                    catch(Exception)
                    {
                        Thread.Sleep(500);
                    }
                } while (!done && w.ElapsedMilliseconds < 5000);

                if (done && !string.IsNullOrEmpty(tmpFile))
                {
                    string newFileName = Path.ChangeExtension(Path.GetFileName(d.SourcePath), ".jpg");
                    MoveScreenshotAndShowMessage(tmpFile, newFileName);
                    if (Settings.Default.DeleteBmpOriginal)
                        File.Delete(d.SourcePath);
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Error converting screenshot: " + ex);
            }
        }
    }
}
